﻿/*
 * Below is sample script
 * 
 * =====================================================================
 * References:
 * http://blog.csdn.net/Donjuan/article/details/3865026
 * 
 * =====================================================================
 * 
<root Name="Test" ResultFolder="G:\C#_Projects\ScreenTester\_Results">
	<OperationBlock Name="OperIE">
    <Keyboard Value="VK_LWIN,VK_R"/>
    <Separator WaitSeconds="1" />
    <InputText Value="iexplore www.google.com"></InputText>
    <Keyboard Value="VK_RETURN" />
    <Separator WaitSeconds="2"/>
  </OperationBlock>
  <OperationBlock Name="AdjustWindows">
    <FocusWindows Width="1024" Height="768"/>
    <Separator WaitSeconds="1"/>
    <FocusWindows ApplicationName="notepad" ApplicationTitle="Untitled - Notepad" Width="1024" Height="768"/>
    <Separator WaitSeconds="1"/>
  </OperationBlock>
  <OperationBlock Name="MoveMouse">
    <Mouse Value="MOUSEEVENTF_MOVE,MOUSEEVENTF_ABSOLUTE" X="1024" Y="768" />
    <Separator WaitSeconds="1"/>
  </OperationBlock>
</root>
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using ScreenTester.Exceptions;
using System.Collections;
using System.IO;
using System.Drawing;

namespace ScreenTester
{
    public delegate ExecutiveOperation[] ElementParser(XElement xelem);
    class ExecutiveOperationBlock
    {
        public ExecutiveOperation[] OperCollection;
        public string BlockName;
    }
    public class Parser
    {
        public string ElementName;
        public ElementParser Parser_;
    }
    public class TestCaseRunner
    {
        #region Constants
        /// <summary>
        /// Test cases file extension
        /// </summary>
        public static readonly string EXTENSION = "scrntst";
        /// <summary>
        /// Node: 'Root'name of the root
        /// </summary>
        public static readonly string XML_ROOT = "Root";
        public static readonly string XML_VALUE = "Value";

        /// <summary>
        /// Attribute: name of the test case
        /// </summary>
        public static readonly string XML_NAME = "Name";
        /// <summary>
        /// Attribute: 'ResultFolder'folder path of the results
        /// </summary>
        public static readonly string XML_RESULT_FOLDER = "ResultFolder";
        /// <summary>
        /// Node: 'OperationBlock', Operation block. And the end of the block, there will be a snapshot
        /// </summary>
        public static readonly string XML_OPERATION_BLOCK = "OperationBlock";
        /// <summary>
        /// Node: 'Keyboard' event. 
        ///     parameter: 'Value' 
        /// </summary>
        public static readonly string XML_KEYBOARD = "Keyboard";
        /// <summary>
        /// Node: Input text.
        ///     parameter: 'Value'.
        /// </summary>
        public static readonly string XML_TEXT = "InputText";
        /// <summary>
        /// Node: 'Separator' between two operations
        ///     parameter: 'WaitSeconds'
        /// </summary>
        public static readonly string XML_SEPARATOR = "Separator";
        public static readonly string XML_WAITING_SECONDES = "WaitSeconds";

        /// <summary>
        /// Node: mouse event
        ///     parameter: 'Value'. mouse event
        ///     parameter: 'X' for x aix
        ///     parameter: 'Y' for y aix
        /// </summary>
        public static readonly string XML_MOUSE = "Mouse";
        public static readonly string XML_X = "X";
        public static readonly string XML_Y = "Y";

        /// <summary>
        /// Node: Focus on windows
        ///     parameter: 'ApplicationName' for application name.
        ///     parameter: 'ApplicationTitle' for application title.
        ///     parameter: 'Width' for width of the screen size
        ///     parameter: 'Height' for height of the screen size
        /// </summary>
        public static readonly string XML_FORCUS_WINDOWS = "FocusWindows";
        public static readonly string XML_APPLICATION_NAME = "ApplicationName";
        public static readonly string XML_APPLICATION_TITLE = "ApplicationTitle";
        public static readonly string XML_WIDTH = "Width";
        public static readonly string XML_HEIGHT = "Height";


        #endregion


        #region Properties
        public string TestName { get { return _testName; } }
        public string ResultFolderPath { get { return _resultFolderPath; } }
        #endregion

        #region Fields



        private string _testCaseFileName;
        private string _testName;
        private string _resultFolderPath;
        private List<ExecutiveOperationBlock> _operations;
        private Hashtable _parsers;
        #endregion

        /// <summary>
        /// Test Case Runner
        /// </summary>
        /// <param name="testFileName"></param>
        public TestCaseRunner(string testFileName, string resultFolderPath)
        {
            _testCaseFileName = testFileName;
            _resultFolderPath = resultFolderPath;

            constructParsers();
            // parse the file
            parseFile();
        }

        public void Run()
        {
            // generate result folder
            Directory.CreateDirectory(_resultFolderPath);
            int blockCount = 0;

            Controller controller = Controller.GetInstance();
            foreach (ExecutiveOperationBlock block in _operations)
            {
                Console.WriteLine("Running {0}.Block: {1}", blockCount, block.BlockName);
                int picCound = 1;
                Bitmap[] bmps = controller.ExecuteOperationList(block.OperCollection);

                Console.WriteLine("{0}.Block {1} finished.", blockCount, block.BlockName);
                foreach (Bitmap bmp in bmps)
                {
                    string fileName = string.Format("{0}\\{1}_{2}_{3}.bmp"
                        , _resultFolderPath
                        , _testName
                        , blockCount
                        , picCound);
                    bmp.Save(fileName);

                    picCound++;
                }
                blockCount++;

            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void constructParsers()
        {
            _parsers = new Hashtable();
            _parsers.Add(XML_TEXT, new Parser() { ElementName = XML_TEXT, Parser_ = generateText });
            _parsers.Add(XML_KEYBOARD, new Parser() { ElementName = XML_KEYBOARD, Parser_ = generateKeyboards });
            _parsers.Add(XML_MOUSE, new Parser() { ElementName = XML_MOUSE, Parser_ = generateMouse });
            _parsers.Add(XML_SEPARATOR, new Parser() { ElementName = XML_SEPARATOR, Parser_ = generateSeperator });
            _parsers.Add(XML_FORCUS_WINDOWS, new Parser() { ElementName = XML_FORCUS_WINDOWS, Parser_ = generateApplication });

        }
        /// <summary>
        /// Parse file
        /// </summary>
        private void parseFile()
        {
            Console.WriteLine("Parsing test case script file: {0}"
                , _testCaseFileName);

            XDocument xdoc = XDocument.Load(_testCaseFileName);
            XElement xelem = xdoc.Root;

            XAttribute attr = xelem.Attribute(XML_NAME);
            if (attr == null)
            {
                throw new FileFormatException(string.Format("There should be an attribute called 'Name' in root node."));
            }
            _testName = attr.Value;

            generateOperations(xelem);

            Console.WriteLine(
@"Test name: {0}, result folder: {1},
And there is {2} blocks in this test cases.
-------------------------------------------------------------"
                , _testName
                , _resultFolderPath
                , _operations.Count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xblock"></param>
        private void generateOperations(XElement xroot)
        {
            _operations = new List<ExecutiveOperationBlock>();
            foreach (XElement xblock in xroot.Elements())
            {
                List<ExecutiveOperation> operations = new List<ExecutiveOperation>();
                XAttribute xattr = xblock.Attribute(XML_NAME);
                if (xattr == null)
                {
                    throw new FileFormatException("There should be block name.");
                }

                foreach (XElement xoper in xblock.Elements())
                {
                    string name = xoper.Name.LocalName;
                    object obj = _parsers[name];

                    Parser parser = (Parser)obj;
                    ExecutiveOperation[] single_operations = parser.Parser_(xoper);
                    operations.AddRange(single_operations);
                }
                _operations.Add(new ExecutiveOperationBlock()
                {
                    OperCollection = operations.ToArray<ExecutiveOperation>(),
                    BlockName = xattr.Value
                });
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private static ExecutiveOperation[] generateText(XElement xelem)
        {
            // create a new single operator
            XAttribute attr = xelem.Attribute(XML_VALUE);
            if (attr == null)
            {
                throw new FileFormatException("Text block should contain attribute 'Value'.");
            }
            // parse keys value, get the bination
            return new ExecutiveOperation[] { new TextInputOperation(attr.Value) };
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private static ExecutiveOperation[] generateSeperator(XElement xelem)
        {
            int waitingTime = -1;
            XAttribute attr = xelem.Attribute(XML_WAITING_SECONDES);
            if (attr == null)
            {
                throw new FileFormatException("Seperator block should contain 'WaitSeconds' attribute.");
            }
            try
            {
                waitingTime = Convert.ToInt32(attr.Value) * 1000;
                if (waitingTime <= 0)
                {
                    throw new FileFormatException("WaitSeconds should be positive number");
                }
                return new ExecutiveOperation[] { new SeparatorOperation(waitingTime) };
            }
            catch (Exception e)
            {
                throw new FileFormatException(string.Format(
@"WaitSeconds should be positive number
---------------------------------
{0}"
                    , e.ToString()));
            }
        }
        /// <summary>
        /// Generate Application operation
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private static ExecutiveOperation[] generateApplication(XElement xelem)
        {
            // create a new single operator
            // parse keys value, get the bination
            XAttribute attrName = xelem.Attribute(XML_APPLICATION_NAME);
            XAttribute attrTitle = xelem.Attribute(XML_APPLICATION_TITLE);


            XAttribute attrWidth = xelem.Attribute(XML_WIDTH);
            XAttribute attrHeight = xelem.Attribute(XML_HEIGHT);

            if (attrWidth == null || attrHeight == null)
            {
                throw new FileFormatException("Application operation block should contain both width and height attributes.");
            }
            try
            {
                int width = Convert.ToInt32(attrWidth.Value);
                int height = Convert.ToInt32(attrHeight.Value);

                if (width <= 0 || height <= 0)
                {
                    throw new FileFormatException("Width or Height is not positive number.");
                }

                if (attrName == null && attrTitle == null)
                {
                    return new ExecutiveOperation[] { new ApplicationOperation(width, height) };
                }
                if (attrTitle == null)
                {
                    throw new FileFormatException("Application block should contain attribute 'ApplicationTitle'.");
                }
                if (attrName == null)
                {
                    throw new FileFormatException("Application block should contain attribute 'ApplicationName'.");
                }
                return new ExecutiveOperation[] { new ApplicationOperation(attrName.Value, attrTitle.Value, width, height) };
            }
            catch (Exception e)
            {
                throw new FileFormatException(string.Format(
@"Width or Height is not positive number.
-------------------------------------------
{0}",
                     e.ToString()));
            }

        }
        /// <summary>
        /// Parse xml to mouse operation
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private static ExecutiveOperation[] generateMouse(XElement xelem)
        {
            // create a new single operator
            // parse keys value, get the bination
            List<MouseEventFlag> keys = new List<MouseEventFlag>();
            XAttribute attr = xelem.Attribute(XML_VALUE);
            if (attr == null)
            {
                throw new FileFormatException("MouseEvent block should contain attribute 'Value'.");
            }
            string value = attr.Value;
            string[] tokens = value.Split(',');


            foreach (string str in tokens)
            {
                MouseEventFlag key = (MouseEventFlag)Enum.Parse(typeof(MouseEventFlag), str);
                keys.Add(key);
            }

            XAttribute attrX = xelem.Attribute(XML_X);
            XAttribute attrY = xelem.Attribute(XML_Y);
            if (attrX == null && attrY == null)
            {
                return new ExecutiveOperation[] { new MouseOperation(keys.ToArray<MouseEventFlag>()) };
            }
            if (attrX == null || attrY == null)
            {
                throw new FileFormatException("MouseEvent block should contain both X and Y attributes.");
            }
            try
            {
                int x = Convert.ToInt32(attrX.Value);
                int y = Convert.ToInt32(attrY.Value);

                if (x <= 0 || y <= 0)
                {
                    throw new FileFormatException("X or Y is not positive number.");
                }
                return new ExecutiveOperation[] { new MouseOperation(x, y, keys.ToArray<MouseEventFlag>()) };
            }
            catch (Exception e)
            {
                throw new FileFormatException(string.Format(
@"X or Y is not positive number.
-------------------------------------------
{0}",
                     e.ToString()));
            }

        }
        /// <summary>
        /// Parse xml to keyboard operation
        /// </summary>
        /// <param name="xelem"></param>
        /// <param name="operations"></param>
        private static ExecutiveOperation[] generateKeyboards(XElement xelem)
        {
            // parse keys value, get the bination
            List<Keyboard> keys = new List<Keyboard>();
            XAttribute attr = xelem.Attribute(XML_VALUE);
            if (attr == null)
            {
                throw new FileFormatException("Keyboard should contain the attribute called 'Value'");
            }
            string value = attr.Value;
            string[] tokens = value.Split(',');

            foreach (string str in tokens)
            {
                Keyboard key = (Keyboard)Enum.Parse(typeof(Keyboard), str);
                keys.Add(key);
            }

            return new ExecutiveOperation[] { new KeyboardOperation(keys.ToArray<Keyboard>()) };
        }

    }
}
